Skip to content

fix(cli): resolve @mentions wrapped in markdown emphasis and skip code regions#2547

Open
bosoud wants to merge 2 commits into
block:mainfrom
bosoud:fix/cli-mention-emphasis-delimiters
Open

fix(cli): resolve @mentions wrapped in markdown emphasis and skip code regions#2547
bosoud wants to merge 2 commits into
block:mainfrom
bosoud:fix/cli-mention-emphasis-delimiters

Conversation

@bosoud

@bosoud bosoud commented Jul 23, 2026

Copy link
Copy Markdown

Fixes #2526

Problem

buzz messages send silently dropped any @mention wrapped in markdown emphasis — **@Name**, *@Name*, _@Name_ — or preceded by ( / ||. The message posted with zero p tags, exit 0, no warning, so no one was notified and no buzz-acp agent woke. Desktop's TypeScript parser (hasMention.ts) was fixed for exactly this in 0f93ef44 (#328); the Rust parser never got the equivalent, and none of the 69 existing mention tests contained an emphasis character next to an @.

Changes

Following the direction in the issue — widen the existing Rust predicates against one shared delimiter definition rather than porting the TS regex:

crates/buzz-sdk/src/mentions.rs

  • New is_mention_lead(prev, prev2) — the single definition of "what may precede an @": start-of-string, ASCII whitespace, (, emphasis */_, or spoiler ||. Both extract_at_names and extract_at_mentions_with_known now use it, so the two extraction paths can no longer drift from each other.
  • is_word_boundary widened to accept trailing *, _, and || (single | intentionally stays a non-boundary, matching TS), so **@Will Pfleger** matches the known multi-word name instead of falling through to a useless will token.
  • Doc comments on both predicates point at hasMention.ts as the counterpart that must stay in agreement.

crates/buzz-cli/src/commands/messages.rs

  • resolve_content_mentions now applies strip_code_regions before @name extraction (previously only NIP-27 URI extraction was stripped). An @name inside a fenced block or backtick span no longer fires a notification — the false-positive mirror of the reported false negative.
  • buzz messages send prints a warning to stderr when the (code-stripped) content contains @mention candidates but zero mentions resolved — the independent mitigation suggested in the issue, so a zero-recipient send is no longer silent. Email addresses and @ never count as candidates, so ordinary prose does not warn.

Tests

  • 10 new unit tests in buzz-sdk covering bold/italic/spoiler/paren leads, trailing emphasis on multi-word names, the single-| non-delimiter, emails inside emphasis, code-region stripping, and the deliberate _@carol_ fallback-tokenizer trade-off (the _ glues to the name in the fallback path only; the known-names path resolves it correctly — pinned in a test with a comment).
  • 5 new tests in buzz-cli covering the send-pipeline regression case (**@Atlas**), code-region skipping, and the mentions_went_unresolved warning predicate.
  • cargo test -p buzz-sdk -p buzz-cli, cargo clippy --all-targets, and cargo fmt all clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CqMxL1inNtU7icywZxbrGY

@bosoud
bosoud requested a review from a team as a code owner July 23, 2026 16:19
…e regions

An @mention written as **@name**, *@name*, _@Name_, (@name) or ||@name||
was silently dropped by `buzz messages send`: the Rust parser only
accepted an @ preceded by whitespace, so the event carried zero p tags
and nobody was notified. Desktop's TypeScript parser was fixed for this
in 0f93ef4 (block#328); the Rust side never got the equivalent.

Bring buzz-sdk's mention parser to behavioural parity with
desktop/src/features/messages/lib/hasMention.ts:

- Accept `*`, `_`, `(` and `||` as leading delimiters, via a single
  shared `is_mention_lead` predicate used by both `extract_at_names`
  and `extract_at_mentions_with_known` (no second delimiter list to
  drift).
- Widen `is_word_boundary` to accept trailing `*`, `_` and `||`, so
  emphasis no longer breaks multi-word display-name matching.
- Strip code regions before @name extraction in the CLI send path
  (previously only NIP-27 URI extraction was stripped), so @NAMEs in
  code samples no longer fire notifications.
- Warn on stderr when content carries @mention candidates but zero
  mentions resolved — silent zero-recipient sends were the costly part.

Fixes block#2526

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CqMxL1inNtU7icywZxbrGY
Signed-off-by: Abdulwahab <bosoud@me.com>
@bosoud
bosoud force-pushed the fix/cli-mention-emphasis-delimiters branch from 3e7dcdb to 7da0dd6 Compare July 23, 2026 16:53
@SeanGearin

Copy link
Copy Markdown

Nice — this matches the direction from the triage notes, and the zero-resolved-mentions warning is a good addition. Two things worth a look:

  1. Table pipes. Keeping a single | a non-boundary means a mention flush against markdown table pipes (|@Atlas|) still resolves nothing, while f9402f75 in the issue shows table mentions are expected to tag (that one only worked because of surrounding spaces). Accepting a single | on both sides is a strict superset of || and keeps the delimiter predicate single-character. Repro: extract_at_mentions_with_known("|@Atlas|status|", &["Atlas"]) — currently [], expected ["atlas"].

  2. There's a third parser this doesn't reach. resolve_mention_pubkeys in crates/buzz-relay/src/workflow_sink.rs is an independent @-mention parser — its doc comment says it "defines the matching contract" for workflow-emitted kind:9 p tags, and its left-boundary check (workflow_sink.rs:102) still accepts only start-of-string / whitespace / (. Those p tags are what ACP agent wake is gated on (event_mentions_agent, buzz-acp/src/lib.rs:2656), so a workflow-generated **@Robby** emits zero p tags — the exact stalled-handoff failure mode from this issue, on a surface where the text is machine-generated. Nothing this PR needs to absorb, but the issue's "longer term the parsers should be one implementation" note actually spans three parsers, not two — worth a follow-up either way.

Review feedback on block#2547: a mention flush against markdown table pipes
(|@atlas|) resolved nothing because a lone `|` was neither a lead nor a
boundary. Accept a single `|` on both sides — a strict superset of the
spoiler `||` case that also keeps both predicates single-character.
Documented as a deliberate superset of the Desktop parser's `||`-only
regex so a future parity sweep doesn't regress it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CqMxL1inNtU7icywZxbrGY
Signed-off-by: Abdulwahab <bosoud@me.com>
@bosoud

bosoud commented Jul 24, 2026

Copy link
Copy Markdown
Author

Thanks for the review @SeanGearin!

  1. Table pipes — done in 0a39d45. A single | is now accepted as both lead and boundary; your repro (|@Atlas|status|["atlas"]) is pinned as a test, along with @Will Pfleger|x resolving the full known name. It also simplified both predicates back to single-character checks (the two-char || special case is gone). Documented in the doc comments as a deliberate superset of the TS regex's ||-only rule so a future parity sweep doesn't "fix" it backwards.

  2. Third parser — confirmed: is_left_boundary in workflow_sink.rs accepts only start / whitespace / ( (its right side is already emphasis-tolerant, though a trailing _ extends the name there). Filed workflow_sink's @-mention parser is a third divergent implementation — workflow-emitted **@Name** emits zero p tags #2686 to track it, with a note that since buzz-relay already depends on buzz-sdk, the cheapest convergence is for workflow_sink to call the now-shared is_mention_lead — three hand-synced parsers down to two. Happy to pick that up as a follow-up PR once this one settles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Bold-wrapped @mentions (**@Name**) send zero p tags from buzz-cli — no notification, exit 0, no warning

2 participants